#include "head.h"
#include <stdio.h>

/**
 * @brief 接收文件数据并写入目标文件，同时记录传输进度
 * @param clientSockfd 客户端套接字（用于接收数据）
 * @param fd 目标文件描述符（存储接收的数据）
 * @param fd_progress 进度文件描述符（记录当前传输的字节数）
 * @param curFileSize 当前已接收的字节数
 * @param fileSize 文件总大小
 * @param MD5_progress 进度文件的文件名（用于传输完成后删除）
 * @return 成功返回0，失败返回-1
 */
int recvfile(int clientSockfd, int fd, int fd_progress, int curFileSize, int fileSize, char *MD5_progress) {
    // 1. 调整目标文件大小为最终大小（避免动态扩展）
    ftruncate(fd, fileSize);

    // 2. 将目标文件映射到内存（mmap），方便直接写入
    char *p = (char *)mmap(NULL, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        perror("mmap failed");
        TLVResponse(clientSockfd, "Mmap failed.", IS_NOT_OK);
        return -1;
    }
    TLVResponse(clientSockfd, "Mmap success.", IS_OK);
    // 3. 循环接收数据，直到接收完所有字节
    while (fileSize - curFileSize > 0) {
        // 从客户端接收数据（每次最多4096字节）
        int ret = recv(clientSockfd, p + curFileSize, 4096, 0);
        if (ret <= 0) {
            printf("Puts stopped due to recv error.\n");
            munmap(p, fileSize); // 解除内存映射
            return -1;
        }

        // 更新已接收的字节数
        curFileSize += ret;

        // 4. 实时更新进度文件（记录当前传输位置）
        lseek(fd_progress, 0, SEEK_SET); // 跳到进度文件开头
        write(fd_progress, &curFileSize, sizeof(int)); // 写入最新进度
    }

    // 5. 传输完成后清理进度文件
    remove(MD5_progress);

    // 6. 通知客户端传输成功
    TLVResponse(clientSockfd, "Puts success.", IS_OK);
    printf("Puts success.\n");

    // 7. 解除内存映射
    munmap(p, fileSize);
    return 0;
}

/**
 * @brief 处理客户端的文件上传请求（PUT命令）
 * @param clientSockfd 客户端套接字
 * @param train 接收到的命令数据（包含文件MD5和大小）
 * @return 始终返回0（错误通过TLVResponse通知客户端）
 */
int dealPutsOrder(int clientSockfd, train_t *train) {
    // 1. 从命令数据中提取文件MD5和大小
    char MD5[1024] = {0};
    strcpy(MD5, train->buf); // 复制MD5值

    // 清空命令缓冲区，准备接收下一个数据包
    bzero(train, sizeof(train_t));
    recvOrderTrain(clientSockfd, train); // 接收文件大小数据包

    // 2. 解析文件总大小
    int fileSize = atoi(train->buf);

    // 3. 创建目标文件（以可读写方式打开，若存在则清空）
    int fd = open(MD5, O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        perror("open target file failed");
        TLVResponse(clientSockfd, "Server error: cannot create file.", IS_NOT_OK);
        return -1;
    }

    // 4. 创建/打开进度文件（记录当前传输的字节数）
    char MD5_progress[1024] = {0};
    sprintf(MD5_progress, "%s_progress", MD5); // 进度文件名：MD5_progress
    int fd_progress = open(MD5_progress, O_RDWR | O_CREAT, 0666);
   
    // 5. 初始化进度文件（如果是新创建的文件）
    int ret = lseek(fd_progress, 0, SEEK_END);
    if (ret == 0) {
        int curSize = 0;
        write(fd_progress, &curSize, sizeof(int)); // 写入初始进度0
        lseek(fd_progress, 0, SEEK_SET); // 重置文件偏移量
    }

    // 6. 读取当前传输进度
    int curFileSize;
    read(fd_progress, &curFileSize, sizeof(int));

    // 7. 将当前进度发送给客户端（用于断点续传）
    char retbuf[16] = {0};
    sprintf(retbuf, "%d", curFileSize);
    TLVResponse(clientSockfd, retbuf, FILE_LEN);

    // 8. 判断是否需要传输（如果已完成，直接关闭连接）
    if (fileSize == curFileSize) {
        printf("Puts already completed.\n");
        close(fd);
        close(fd_progress);
        close(clientSockfd);
        return 0;
    } else {
        // 9. 调用recvfile接收剩余数据
        recvfile(clientSockfd, fd, fd_progress, curFileSize, fileSize, MD5_progress);
    }

    // 10. 关闭文件描述符和套接字
    close(fd);
    close(fd_progress);
    close(clientSockfd);
    return 0;
}

/**
 * @brief 处理客户端的文件下载请求（GET命令）
 * @param clientSockfd 客户端套接字
 * @param train 接收到的命令数据（包含文件MD5和传输范围）
 * @return 成功返回0，失败返回-1
 */
int dealGetsOrder(int clientSockfd, train_t *train) {
    // 1. 从命令数据中提取文件MD5
    char MD5[1024] = {0};
    strcpy(MD5, train->buf); // 复制MD5值到本地缓冲区

    // 2. 打开目标文件（只读模式）
    int fd = open(MD5, O_RDWR);
    if (fd == -1) {
        perror("open file failed");
        TLVResponse(clientSockfd, "Open failed.", IS_NOT_OK);
        close(clientSockfd);
        return -1;
    }

    // 3. 获取文件总大小
    int fileSize = lseek(fd, 0, SEEK_END); // 跳到文件末尾，获取偏移量（即文件大小）
    lseek(fd, 0, SEEK_SET);                // 重置文件偏移量到开头
    
    ftruncate(fd, fileSize);

    // 4. 将文件映射到内存（mmap），方便高效读取
    char *p = (char *)mmap(NULL, fileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        perror("mmap failed");
        close(fd);
        TLVResponse(clientSockfd, "Mmap failed.", IS_NOT_OK);
        close(clientSockfd);
        return -1;
    }

    TLVResponse(clientSockfd, "OK.", IS_OK);

    // 5. 清空命令缓冲区，准备接收客户端发送的传输范围参数
    bzero(train, sizeof(train_t));
    
    // 6. 接收客户端发送的起始偏移量（from）
    int ret = recvOrderTrain(clientSockfd, train);
    if (ret != 0) { // 接收失败（如客户端断开连接）
        printf("Client disconnected while sending 'from' offset.\n");
        munmap(p, fileSize);
        close(fd);
        close(clientSockfd);
        return -1;
    }
    off_t from = atoi(train->buf); // 解析起始偏移量

    // 7. 清空缓冲区，准备接收下一个参数
    bzero(train, sizeof(train_t));
    
    // 8. 接收客户端发送的传输大小（size）
    ret = recvOrderTrain(clientSockfd, train);
    if (ret != 0) { // 接收失败
        printf("Client disconnected while sending 'size'.\n");
        munmap(p, fileSize);
        close(fd);
        close(clientSockfd);
        return -1;
    }
    size_t size = atoi(train->buf); // 解析传输大小

    // 9. 使用sendfile高效发送文件数据（零拷贝优化）
    sendfile(clientSockfd, fd, &from, size);

    // 10. 清理资源
    munmap(p, fileSize); // 解除内存映射
    close(fd);           // 关闭文件
    close(clientSockfd); // 关闭客户端套接字
    return 0;
}

